package cuatroenraya.jugadores;

//import cuatroenrayabbc.Jugador;
import cuatroenraya.Jugador;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

/**
 * Clase Jugador que juega basándose en simulaciones del rival y búsquedas del
 * mejor resultado.
 * @author 
 * Daniel Acevedo (Leg: 49358)
 * Juan Brusasca (Leg: 38227)
 * Marcelo Busico (Leg: 49145)
 * David Canteros (Leg: 47616)
 */
public class JugadorBBC extends Jugador {

    private int posicionJugador;
    private Tablero tablero;
    private Set<Raya> rayasPropias = new TreeSet<Raya>();
    private Set<Raya> rayasRival = new TreeSet<Raya>();
    private Set<Raya> rayasVacias = new TreeSet<Raya>();
    private int profundidadDeBusqueda;

    public JugadorBBC(boolean isJugadorA) {
        nombre = "JugadorBBC";
        if (isJugadorA) {
            posicionJugador = 1;
        } else {
            posicionJugador = 2;
        }
    }

    /**
     * Implementación del método jugar.
     * @param tablero Tablero actual.
     * @return Coordenadas jugadas.
     */
    @Override
    public int[] jugar(int[][] tableroPrimitivo) {
        this.tablero = new Tablero(tableroPrimitivo);
        this.profundidadDeBusqueda = 3;

        //Genera las rayas en las listas.
        generarRayas(tablero);

        imprimirTablero(
                String.format("Tablero recibido por jugador %s (%s):",
                posicionJugador, (posicionJugador == 1 ? "0" : "X")), tablero);

        Resultado res = solicitarJugada();

        if (res == null) {
            throw new RuntimeException("Chuck Norris no devolvió una jugada.");
        }

        return res.getJugada();
    }

    /**
     * Ejecuta una jugada posible (Método llamado recursivamente).
     * @return Resultado de la jugada.
     */
    private Resultado solicitarJugada() {
        Resultado jugada = null;

        jugada = jugarParaEmpezar();
        if (jugada != null) {
            return jugada;
        }

        jugada = jugarParaGanar();
        if (jugada != null) {
            return jugada;
        }

        jugada = jugarParaDefender();
        if (jugada != null) {
            return jugada;
        }

        jugada = jugadaChuckNorris();
        if (jugada != null) {
            return jugada;
        }

        if (jugada == null) {
            for (int i = 0; i < 8; i++) {
                for (int j = 0; j < 8; j++) {
                    if (tablero.fichas[i][j].isVacio()) {
                        Resultado resultado = new Resultado();
                        resultado.setJugada(new int[]{i, j});
                        resultado.setCantidadJugadasParaGanar(Integer.MAX_VALUE);
                        resultado.setCantidadJugadasParaPerder(Integer.MAX_VALUE);
                        resultado.setGanaJugadorPropio(false);
                        return resultado;
                    }
                }
            }
        }

        throw new IllegalStateException("No se encontró lugar vacío para jugar.");
    }

    /**
     *  Colocar una ficha en un casillero para iniciar la partida.
     */
    private Resultado jugarParaEmpezar() {
        if (rayasPropias.isEmpty() && rayasRival.isEmpty()) {
            int[] coordenadas = new int[2];
            coordenadas[0] = 3;
            coordenadas[1] = 3;

            Resultado resultado = new Resultado();
            resultado.setJugada(coordenadas);
            resultado.setCantidadJugadasParaGanar(Integer.MAX_VALUE);
            resultado.setCantidadJugadasParaPerder(Integer.MAX_VALUE);
            resultado.setGanaJugadorPropio(false);

            return resultado;
        }
        return null;
    }

    /**
     *  Colocar una ficha en un casillero que nos otroga la victoria directa
     */
    private Resultado jugarParaGanar() {
        for (Raya raya : rayasPropias) {
            if (raya.getCriticidad() == Raya.CRITICIDAD_URGENTE) {
                for (int i = 0; i < raya.getFichas().length; i++) {

                    Ficha ficha = raya.getFichas()[i];

                    if (ficha.isVacio()) {
                        int[] coordenadas = new int[2];
                        coordenadas[0] = ficha.coordenadaX;
                        coordenadas[1] = ficha.coordenadaY;

                        Resultado resultado = new Resultado();
                        resultado.setJugada(coordenadas);
                        resultado.setCantidadJugadasParaGanar(0);
                        resultado.setCantidadJugadasParaPerder(Integer.MAX_VALUE);
                        resultado.setGanaJugadorPropio(true);

                        return resultado;
                    }
                }
            }
        }
        return null;
    }

    /**
     * Colocar la ficha en el casillero del rival que evite su victoria inmediata
     */
    private Resultado jugarParaDefender() {
        for (Raya raya : rayasRival) {
            if (raya.getCriticidad() == Raya.CRITICIDAD_URGENTE) {
                for (int i = 0; i < raya.getFichas().length; i++) {

                    Ficha ficha = raya.getFichas()[i];

                    if (ficha.isVacio()) {
                        int[] coordenadas = new int[2];
                        coordenadas[0] = ficha.coordenadaX;
                        coordenadas[1] = ficha.coordenadaY;

                        Resultado resultado = new Resultado();
                        resultado.setJugada(coordenadas);
                        resultado.setCantidadJugadasParaGanar(Integer.MAX_VALUE);
                        resultado.setCantidadJugadasParaPerder(1);
                        resultado.setGanaJugadorPropio(false);

                        return resultado;
                    }
                }
            }
        }
        return null;
    }

    /**
     * Juega las fichas de modo inteligente planificando y simulando ante situaciones
     * no críticas.
     */
    private Resultado jugadaChuckNorris() {
        if (profundidadDeBusqueda < 0) {
            return null;
        }

        //En caso que no encuentre rayas propias jugar en un lugar en blanco
        //que tenga la mayor cantidad de lugares vacios en el contorno.
        if (rayasPropias.isEmpty()) {
            Ficha fichaAJugar = null;
            int cantidadLugaresVacios = 0;

            //Intenta poner una ficha en una raya vacía
            for (Raya raya : rayasVacias) {
                for (int i = 0; i < 4; i++) {
                    int lugares = raya.getFichas()[i].getCantidadFichasEnContorno(
                            tablero, Owner.VACIA);
                    if (lugares > cantidadLugaresVacios) {
                        cantidadLugaresVacios = lugares;
                        fichaAJugar = raya.getFichas()[i];
                    }
                }
            }
            //Si no encuentra ficha a jugar, entonces trata de poner una ficha
            //en las rayas del rival, si no se puede retorna null.
            if (fichaAJugar == null) {
                if (rayasRival.isEmpty()) {
                    for (Raya raya : rayasRival) {
                        for (int i = 0; i < 4; i++) {
                            if (raya.getFichas()[i].isVacio()) {
                                fichaAJugar = raya.getFichas()[i];
                            }
                        }
                    }
                } else {
                    return null;
                }
            }

            Resultado resultado = new Resultado();
            resultado.setJugada(new int[]{fichaAJugar.getCoordenadaX(), fichaAJugar.getCoordenadaY()});
            resultado.setCantidadJugadasParaGanar(3);
            resultado.setCantidadJugadasParaPerder(Integer.MAX_VALUE);
            resultado.setGanaJugadorPropio(true);
            return resultado;
        }

        //Realiza una búsqueda de estados posibles y guarda sus resultados.
        List<Resultado> resultados = new LinkedList<Resultado>();
        boolean cortarBusqueda = false;

        for (Raya raya : rayasRival) {
            //Corta la simulación si alguno de los resultados posibles
            //es próximo a ganar.
            if (cortarBusqueda) {
                break;
            }
            for (Resultado resultado : resultados) {
                if (resultado.getCantidadJugadasParaPerder() <= 1) {
                    cortarBusqueda = true;
                }
            }
            simularJugadaRival(raya, resultados);
        }

        cortarBusqueda = false;
        for (Raya raya : rayasPropias) {
            //Corta la simulación si alguno de los resultados posibles
            //es próximo a ganar.
            if (cortarBusqueda) {
                break;
            }
            for (Resultado resultado : resultados) {
                if (resultado.getCantidadJugadasParaGanar() == 0) {
                    cortarBusqueda = true;
                }
            }
            simularJugadaRival(raya, resultados);
        }

        Resultado resultadoFinalGanador = null;
        Resultado resultadoFinalPerdedor = null;
        Resultado resultadoFinal = new Resultado();
        LinkedList<Resultado> resultadosGanadoresBest = new LinkedList<Resultado>();
        LinkedList<Resultado> resultadosPerdedoresBest = new LinkedList<Resultado>();

        //Recorre todos los resultados devueltos por la búqueda y obtiene el nro de jugadas opminas para ganar o perder
        int cantidadJugadasParaGanar = Integer.MAX_VALUE;
        int cantidadJugadasParaPerder = Integer.MAX_VALUE;

        //Dentro de este for buscar la mejor combinación de resultados que
        //maximice la cantidad de fichas en una línea.
        for (Resultado resultado : resultados) {
            if (resultado.ganaJugadorPropio) {
                //Busca jugadas para ganar
                if (resultado.getCantidadJugadasParaGanar() < cantidadJugadasParaGanar) {
                    resultadoFinalGanador = resultado;
                    cantidadJugadasParaGanar = resultado.getCantidadJugadasParaGanar();
                }
            } else {
                //Busca jugadas que puedan conducir a la pérdida
                if (resultado.getCantidadJugadasParaPerder() < cantidadJugadasParaPerder) {
                    resultadoFinalPerdedor = resultado;
                    cantidadJugadasParaPerder = resultado.getCantidadJugadasParaPerder();
                }
            }
        }

        // recorre nuevamente los resultados y guarda todos los optimos ganadores y los perdedores
        for (Resultado resultado : resultados) {
            if (resultado.isGanaJugadorPropio() &&
                    (resultado.getCantidadJugadasParaGanar() == cantidadJugadasParaGanar)) {
                resultadosGanadoresBest.add(resultado);
            }
            if (!resultado.isGanaJugadorPropio() &&
                    (resultado.getCantidadJugadasParaPerder() == cantidadJugadasParaPerder)) {
                resultadosPerdedoresBest.add(resultado);
            }
        }

        resultadoFinalGanador = getMejorResultado(resultadosGanadoresBest, Owner.PROPIA);
        resultadoFinalPerdedor = getMejorResultado(resultadosPerdedoresBest, Owner.RIVAL);

        //Verifica que haya resultado ganador y/o perdedor y lo retorna, utilizando el mejor de los resultados posibles.
        if (resultadoFinalGanador == null) {
            if (resultadoFinalPerdedor == null) {
                return null;
            } else {
                resultadoFinal.setCantidadJugadasParaGanar(Integer.MAX_VALUE);
                resultadoFinal.setCantidadJugadasParaPerder(resultadoFinalPerdedor.getCantidadJugadasParaPerder() + 1);
                resultadoFinal.setGanaJugadorPropio(false);
                resultadoFinal.setJugada(resultadoFinalPerdedor.getJugada());
                return resultadoFinal;
            }
        } else {
            if (resultadoFinalPerdedor == null) {
                resultadoFinal.setCantidadJugadasParaGanar(resultadoFinalGanador.getCantidadJugadasParaGanar());
                resultadoFinal.setCantidadJugadasParaPerder(Integer.MAX_VALUE);
                resultadoFinal.setGanaJugadorPropio(true);
                resultadoFinal.setJugada(resultadoFinalGanador.getJugada());
                return resultadoFinal;
            }
        }

        if (resultadoFinalGanador != null) {
            resultadoFinal.setCantidadJugadasParaGanar(resultadoFinalGanador.getCantidadJugadasParaGanar());
        } else {
            resultadoFinal.setCantidadJugadasParaGanar(Integer.MAX_VALUE);
        }

        if (resultadoFinalPerdedor != null) {
            resultadoFinal.setCantidadJugadasParaPerder(resultadoFinalPerdedor.getCantidadJugadasParaPerder() + 1);
        } else {
            resultadoFinal.setCantidadJugadasParaPerder(Integer.MAX_VALUE);
        }

        //Retorna el mejor resultado entre ganador y perdedor.
        if (resultadoFinal.getCantidadJugadasParaGanar() <=
                resultadoFinal.getCantidadJugadasParaPerder()) {
            resultadoFinal.setGanaJugadorPropio(true);
            resultadoFinal.setJugada(resultadoFinalGanador.getJugada());
        } else {
            resultadoFinal.setGanaJugadorPropio(false);
            resultadoFinal.setJugada(resultadoFinalPerdedor.getJugada());
        }

        return resultadoFinal;
    }

    /**
     * Devuelve el mejor resultado de la lista pasada.
     * @param resultados Resultados a comparar.
     * @param owner Indica si el mejor resultado buscado es Propio o Rival.
     * @return Mejor resultado.
     */
    private Resultado getMejorResultado(LinkedList<Resultado> resultados, Owner owner) {
        Resultado elegido = null;

        int[][] casilleroAElegir = new int[8][8];

        //Recorre todos los mejores resultados y los carga sumando en la matriz casilleroAElegir.
        for (Resultado resultado : resultados) {
            int jugada[] = resultado.getJugada();
            casilleroAElegir[jugada[0]][jugada[1]]++;
        }

        List<Ficha> mejoresFichas = new LinkedList<Ficha>();
        int cantidadRayas = 0;

        //Recorre los casilleros a Elegir y crea una lista con las mejores fichas
        //a jugar, con la misma cantidad de rayas que pasan por el casillero.
        for (int i = 0; i < 8; i++) {
            for (int j = 0; j < 8; j++) {
                if (cantidadRayas < casilleroAElegir[i][j]) {
                    cantidadRayas = casilleroAElegir[i][j];
                    mejoresFichas.clear();
                    mejoresFichas.add(tablero.getFichas()[i][j]);
                } else {
                    if (cantidadRayas == casilleroAElegir[i][j]) {
                        mejoresFichas.add(tablero.getFichas()[i][j]);
                    }
                }
            }
        }

        int cantidadPorFicha = 0;
        for (Ficha ficha : mejoresFichas) {
            for (Resultado resultado : resultados) {
                int[] jugadaDeResultado = resultado.getJugada();
                int[] posicionDeFicha = new int[2];
                posicionDeFicha[0] = ficha.getCoordenadaX();
                posicionDeFicha[1] = ficha.getCoordenadaY();
                if (jugadaDeResultado[0] == posicionDeFicha[0] &&
                        jugadaDeResultado[1] == posicionDeFicha[1]) {

                    if (cantidadPorFicha < ficha.getRayas().size()) {
                        cantidadPorFicha = ficha.getRayas().size();
                        elegido = resultado;
                        break;
                    }
                }
            }
            if (elegido != null) {
                break;
            }
        }

        return elegido;
    }

    private void imprimirTablero(String titulo, Tablero tablero) {
        System.out.println("");
        System.out.println(titulo);
        for (int i = 0; i < 8; i++) {
            String linea = "";
            for (int j = 0; j < 8; j++) {
                if (tablero.getFichas()[i][j].getOwner() == Owner.VACIA) {
                    linea += "  ";
                } else if (tablero.getFichas()[i][j].getOwner() ==
                        (posicionJugador == 1 ? Owner.PROPIA : Owner.RIVAL)) {
                    linea += " 0";
                } else {
                    linea += " X";
                }
            }
            System.out.println(linea);
        }
    }

    /**
     * Simula la jugada del rival y guarda los resultados en la lista de resultados
     * pasadas por parámetro.
     * @param raya Raya a buscar lugar para simular.
     * @param resultados Lista de Resultados acumulados.
     */
    private void simularJugadaRival(Raya raya, List<Resultado> resultados) {
        for (int i = 0; i < 4; i++) {
            Ficha fichaActual = raya.getFichas()[i];
            if (fichaActual.isVacio()) {
                //Corta la simulación si alguno de los resultados posibles
                //es próximo a ganar.
                for (Resultado resultado : resultados) {
                    if (resultado.getCantidadJugadasParaPerder() <= 1 ||
                            resultado.getCantidadJugadasParaGanar() == 0) {
                        return;
                    }
                }

                JugadorBBC rivalSimulado = new JugadorBBC(posicionJugador != 1);
                int fichaCordX = fichaActual.getCoordenadaX();
                int fichaCordY = fichaActual.getCoordenadaY();
                rivalSimulado.tablero = (Tablero) tablero.clone();
                rivalSimulado.tablero.fichas[fichaCordX][fichaCordY].setOwner(Owner.RIVAL);
                rivalSimulado.profundidadDeBusqueda = profundidadDeBusqueda - 1;
                rivalSimulado.generarRayas(rivalSimulado.tablero);
//                imprimirTablero("Tablero Rival Simulado: ", rivalSimulado.tablero);
                Resultado resultado = rivalSimulado.solicitarJugada();
                if (resultado != null) {

                    //Invertir los resultados ya que son del punto de vista del rival.
                    boolean ganaJugadorPropio = !resultado.isGanaJugadorPropio();
                    int cantidadJugadasParaGanar = resultado.getCantidadJugadasParaPerder();
                    int cantidadJugadasParaPerder = resultado.getCantidadJugadasParaGanar();
                    resultado.setGanaJugadorPropio(ganaJugadorPropio);
                    resultado.setCantidadJugadasParaGanar(cantidadJugadasParaGanar);
                    resultado.setCantidadJugadasParaPerder(cantidadJugadasParaPerder);

                    //Agrega el resultado a la lista de resultados.
                    resultados.add(resultado);
                }
            }
        }
    }

    private Owner determinarOwner(int valorCasilla) {
        Owner ownerCasilla;
        if (valorCasilla == 0) {
            ownerCasilla = Owner.VACIA;
        } else if (valorCasilla == posicionJugador) {
            ownerCasilla = Owner.PROPIA;
        } else {
            ownerCasilla = Owner.RIVAL;
        }
        return ownerCasilla;
    }

    /**
     * Busca en el tablero todas las rayas posibles y las carga en la lista
     * correspondiente (propias, rival, vacias).
     */
    private void generarRayas(Tablero tablero) {
        rayasPropias = new TreeSet<Raya>();
        rayasRival = new TreeSet<Raya>();
        rayasVacias = new TreeSet<Raya>();

        //Recorrer horizontales
        for (int i = 0; i < 8; i++) {
            for (int j = 0; j < 5; j++) {
                verificarYCargarRaya(
                        tablero.getFichas()[i][j],
                        tablero.getFichas()[i][j + 1],
                        tablero.getFichas()[i][j + 2],
                        tablero.getFichas()[i][j + 3]);
            }
        }

        //Recorrer verticales
        for (int i = 0; i < 5; i++) {
            for (int j = 0; j < 8; j++) {
                verificarYCargarRaya(
                        tablero.getFichas()[i][j],
                        tablero.getFichas()[i + 1][j],
                        tablero.getFichas()[i + 2][j],
                        tablero.getFichas()[i + 3][j]);
            }
        }

        //Recorrer diagonal 135 grados
        for (int i = 0; i < 5; i++) {
            for (int j = 0; j < 5; j++) {
                verificarYCargarRaya(
                        tablero.getFichas()[i][j],
                        tablero.getFichas()[i + 1][j + 1],
                        tablero.getFichas()[i + 2][j + 2],
                        tablero.getFichas()[i + 3][j + 3]);
            }
        }

        //Recorrer diagonal 45 grados
        for (int i = 0; i < 5; i++) {
            for (int j = 7; j > 2; j--) {
                verificarYCargarRaya(
                        tablero.getFichas()[i][j],
                        tablero.getFichas()[i + 1][j - 1],
                        tablero.getFichas()[i + 2][j - 2],
                        tablero.getFichas()[i + 3][j - 3]);
            }
        }
    }

    /**
     * Verifica la raya y la carga en la lista correspondiente.
     */
    private void verificarYCargarRaya(Ficha ficha1,
            Ficha ficha2, Ficha ficha3, Ficha ficha4) {

        if (ficha1.getOwner() == Owner.VACIA &&
                ficha2.getOwner() == Owner.VACIA &&
                ficha3.getOwner() == Owner.VACIA &&
                ficha4.getOwner() == Owner.VACIA) {

            //La raya está vacía
            Raya raya = new Raya();
            raya.setCriticidad(Raya.CRITICIDAD_NULA);
            raya.setFichas(new Ficha[4]);
            raya.getFichas()[0] = ficha1;
            raya.getFichas()[1] = ficha2;
            raya.getFichas()[2] = ficha3;
            raya.getFichas()[3] = ficha4;

            //Agrega la raya a cada una de las fichas
            ficha1.getRayas().add(raya);
            ficha2.getRayas().add(raya);
            ficha3.getRayas().add(raya);
            ficha4.getRayas().add(raya);

            rayasVacias.add(raya);
            return;
        }

        Ficha[] valores = new Ficha[4];
        valores[0] = ficha1;
        valores[1] = ficha2;
        valores[2] = ficha3;
        valores[3] = ficha4;
        /**
         * recorro toda la raya buscando owner y descartar mixtas
         */
        Owner flag = Owner.VACIA;
        for (int i = 0; i < 4; i++) {
            if (valores[i].getOwner() != Owner.VACIA) {
                if (flag == Owner.VACIA) {
                    flag = valores[i].getOwner();
                } else {
                    if (flag != valores[i].getOwner()) {
                        return;
                    }
                }
            }
        }

        Owner casilla1Owner = ficha1.getOwner();
        Owner casilla2Owner = ficha2.getOwner();
        Owner casilla3Owner = ficha3.getOwner();
        Owner casilla4Owner = ficha4.getOwner();

        //La raya es del owner encontrado.
        Raya raya = new Raya();

        //Determinar criticidad.
        int cantidad = 0;
        if (casilla1Owner != Owner.VACIA) {
            cantidad++;
        }
        if (casilla2Owner != Owner.VACIA) {
            cantidad++;
        }
        if (casilla3Owner != Owner.VACIA) {
            cantidad++;
        }
        if (casilla4Owner != Owner.VACIA) {
            cantidad++;
        }
        switch (cantidad) {
            case 0:
            case 1:
                raya.setCriticidad(Raya.CRITICIDAD_NULA);
                break;
            case 2:
                if (casilla1Owner == Owner.VACIA && casilla4Owner == Owner.VACIA) {
                    raya.setCriticidad(Raya.CRITICIDAD_ALTA);
                } else {
                    raya.setCriticidad(Raya.CRITICIDAD_NORMAL);
                }
                break;
            case 3:
                raya.setCriticidad(Raya.CRITICIDAD_URGENTE);
                break;
            default:
        }

        //Determinar las fichas de la raya.
        raya.setFichas(new Ficha[4]);

        raya.getFichas()[0] = ficha1;
        raya.getFichas()[1] = ficha2;
        raya.getFichas()[2] = ficha3;
        raya.getFichas()[3] = ficha4;

        //Agrega la raya a cada una de las fichas
        ficha1.getRayas().add(raya);
        ficha2.getRayas().add(raya);
        ficha3.getRayas().add(raya);
        ficha4.getRayas().add(raya);

        if (flag == Owner.PROPIA) {
            rayasPropias.add(raya);
        } else {
            rayasRival.add(raya);
        }
    }

    // <editor-fold defaultstate="collapsed" desc="Clases internas">
    /**
     * Clase Raya.
     */
    class Raya implements Comparable<Raya> {

        private Ficha[] fichas = new Ficha[4];
        private int criticidad = 0;
        /**
         * solo tiene ocupado  creo casillero
         */
        public static final int CRITICIDAD_NULA = 0;
        /**
         * solo tiene ocupado un  casillero
         */
        public static final int CRITICIDAD_BAJA = 1;
        /**
         * posee opcupado dos casilleros con fichas contra un borde o ficha rival
         */
        public static final int CRITICIDAD_NORMAL = 2;
        /**
         * posee ocupado dos casilleros contiguos con sus dos extremos libres
         */
        public static final int CRITICIDAD_ALTA = 3;
        /**
         * posee ocupado 3 casilleros
         */
        public static final int CRITICIDAD_URGENTE = 4;

        public Raya() {
        }

        public int getCriticidad() {
            return criticidad;
        }

        public void setCriticidad(int criticidad) {
            this.criticidad = criticidad;
        }

        public Ficha[] getFichas() {
            return fichas;
        }

        /**
         * Calcula los lugares vacios de la raya
         * @return lugares vacios de la raya.
         */
        public int getCantidadLugaresVacios() {
            int vacios = 0;
            for (Ficha ficha : fichas) {
                if (ficha.isVacio()) {
                    vacios++;
                }
            }
            return vacios;
        }

        public void setFichas(Ficha[] fichas) {
            this.fichas = fichas;
        }

        public Owner getPropiedad() {
            for (int i = 0; i < fichas.length; i++) {
                Ficha ficha = fichas[i];
                if (ficha != null) {
                    return ficha.getOwner();
                }
            }
            return Owner.VACIA;
        }

        /**
         * Compara las rayas por criticidad.
         * @param o Otra raya a comparar.
         * @return Diferencia de criticidades entre la otra raya y la actual.
         */
        public int compareTo(Raya o) {
            int res = o.criticidad - criticidad;
            return (res == 0 ? -1 : res);
        }
    }

    /**
     * Clase Ficha.
     */
    class Ficha {

        private int coordenadaX;
        private int coordenadaY;
        private boolean vacio;
        private Owner owner;
        private List<Raya> rayas = new LinkedList<Raya>();

        public Ficha() {
        }

        public Ficha(int coordenadaX, int coordenadaY, boolean vacio, Owner owner) {
            this.coordenadaX = coordenadaX;
            this.coordenadaY = coordenadaY;
            this.vacio = vacio;
            this.owner = owner;
        }

        public Owner getOwner() {
            return owner;
        }

        public void setOwner(Owner owner) {
            this.owner = owner;
        }

        public int getCoordenadaX() {
            return coordenadaX;
        }

        public void setCoordenadaX(int coordenadaX) {
            this.coordenadaX = coordenadaX;
        }

        public int getCoordenadaY() {
            return coordenadaY;
        }

        public void setCoordenadaY(int coordenadaY) {
            this.coordenadaY = coordenadaY;
        }

        public boolean isVacio() {
            return vacio;
        }

        public void setVacio(boolean vacio) {
            this.vacio = vacio;
        }

        public List<Raya> getRayas() {
            return rayas;
        }

        public void setRayas(List<Raya> rayas) {
            this.rayas = rayas;
        }

        /**
         * Calcula la cantidad de fichas del propietario especificado en el contorno
         * de la ficha actual.
         * @param tablero Tablero con todas las fichas.
         * @param owner Propietario de la ficha (Vacio, propio o rival).
         * @return Cantidad de fichas.
         */
        public int getCantidadFichasEnContorno(Tablero tablero, Owner owner) {
            int cantidad = 0;

            int desdeX = (coordenadaX > 0 ? coordenadaX - 1 : 0);
            int hastaX = (coordenadaX < 8 ? coordenadaX + 1 : 8);
            int desdeY = (coordenadaY > 0 ? coordenadaY - 1 : 0);
            int hastaY = (coordenadaY < 8 ? coordenadaY + 1 : 8);

            for (int i = desdeX; i < hastaX; i++) {
                for (int j = desdeY; j < hastaY; j++) {
                    if (tablero.getFichas()[i][j].getOwner() == owner) {
                        cantidad++;
                    }
                }
            }

            return cantidad;
        }
    }

    /**
     * Enumeración que indica el dueño de la ficha/raya.
     */
    enum Owner {

        PROPIA,
        RIVAL,
        VACIA
    }

    /**
     * Clase con resultado simulado.
     */
    class Resultado {

        private boolean ganaJugadorPropio;
        private int cantidadJugadasParaGanar = Integer.MAX_VALUE;
        private int cantidadJugadasParaPerder = Integer.MAX_VALUE;
        int[] jugada;

        public Resultado() {
        }

        public int getCantidadJugadasParaGanar() {
            return cantidadJugadasParaGanar;
        }

        public void setCantidadJugadasParaGanar(int cantidadJugadasParaGanar) {
            this.cantidadJugadasParaGanar = cantidadJugadasParaGanar;
        }

        public int getCantidadJugadasParaPerder() {
            return cantidadJugadasParaPerder;
        }

        public void setCantidadJugadasParaPerder(int cantidadJugadasParaPerder) {
            this.cantidadJugadasParaPerder = cantidadJugadasParaPerder;
        }

        public boolean isGanaJugadorPropio() {
            return ganaJugadorPropio;
        }

        public void setGanaJugadorPropio(boolean ganaJugadorPropio) {
            this.ganaJugadorPropio = ganaJugadorPropio;
        }

        public int[] getJugada() {
            return jugada;
        }

        public void setJugada(int[] jugada) {
            this.jugada = jugada;
        }

        @Override
        public String toString() {
            return String.format(
                    "Jugada: %d %d - ganaJugadorPropio: %s - cantidadJugadasParaGanar: %d - cantidadJugadasParaPerder: %d",
                    jugada[0], jugada[1], (ganaJugadorPropio ? "Si" : "No"), cantidadJugadasParaGanar, cantidadJugadasParaPerder);
        }
    }

    class Tablero implements Cloneable {

        private Ficha[][] fichas;

        public Tablero(int[][] tablero) {
            fichas = new Ficha[8][8];
            for (int i = 0; i < 8; i++) {
                for (int j = 0; j < 8; j++) {
                    Owner casillaOwner = determinarOwner(tablero[i][j]);
                    fichas[i][j] = new Ficha(i, j, casillaOwner == Owner.VACIA, casillaOwner);
                }
            }
        }

        public Tablero(Ficha[][] tablero) {
            fichas = new Ficha[8][8];
            for (int i = 0; i < 8; i++) {
                for (int j = 0; j < 8; j++) {
                    Owner casillaOwner = tablero[i][j].getOwner();
                    fichas[i][j] = new Ficha(i, j, casillaOwner == Owner.VACIA, casillaOwner);
                }
            }
        }

        public Ficha[][] getFichas() {
            return fichas;
        }

        public void setFichas(Ficha[][] fichas) {
            this.fichas = fichas;
        }

        /**
         * Clona el tablero y sus fichas.
         * @return Nuevo tablero con fichas clonadas.
         */
        @Override
        protected Object clone() {
            return new Tablero(fichas);
        }
    }
    // </editor-fold>
}
